package beautiful.butterfly.drds.datasource.connection_contexts.spi;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public abstract class AbstractConnectionContexts {

    public static final Logger logger = LoggerFactory.getLogger(AbstractConnectionContexts.class);
    public static ScheduledExecutorService scheduledExecutorService;

    public static Boolean getBoolean(Properties properties, String key) {
        String property = properties.getProperty(key);
        if ("true".equals(property)) {
            return Boolean.TRUE;
        } else if ("false".equals(property)) {
            return Boolean.FALSE;
        } else {
            throw new IllegalArgumentException(key);
        }

    }

    public static boolean equals(String a, String b) {
        if (a == null) {
            return b == null;
        }
        return a.equals(b);
    }

    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        if (this.scheduledExecutorService != null) {
            throw new IllegalArgumentException("has set");
        }
        this.scheduledExecutorService = scheduledExecutorService;
    }

    // 读写数据模型
    public ReentrantLock lock;
    public Condition notEmpty;// 等待读取数据[左]
    public Condition empty;// 等待写入数据[右]
    //
    public final static int default_initial_size = 0;
    public final static int default_max_active_size = 12;

    public final static int default_min_idle = 0;
    public final static int default_max_wait = -1;
    //
    public final static boolean default_test_on_borrow = false;
    public final static boolean default_test_on_return = false;
    public final static boolean default_while_idle = true;

    public static final long default_time_millis_between_eviction_runs = 60 * 1000l;
    public static final long default_time_millis_between_connect_exception = 30 * 1000;

    public static final long default_min_evictable_idle_time_millis = 1000l * 60l * 30l;
    public static final long default_max_evictable_idle_time_millis = 1000l * 60l * 60l * 7;
    public static final long max_alive_time_millis = -1;
    public final static Object PRESENT = new Object();
    public final AtomicLong errorCount = new AtomicLong();

    public final Map<ConnectionContext, Object> activeConnectionContextToObjectMap = new IdentityHashMap<ConnectionContext, Object>();

    //
    public volatile String jdbcUrl;
    public volatile String username;
    public volatile String password;
    public volatile String defaultCatalog = null;
    //
    // 可读
    public volatile Boolean defaultReadOnly = false;
    // 自动提交
    public volatile boolean defaultAutoCommit = true;
    // 默认的隔离级别
    public volatile Integer defaultTransactionIsolationLevel;

    //
    public volatile Properties properties = new Properties();
    //
    public volatile int initialSize = default_initial_size;
    public volatile int maxActive = default_max_active_size;
    //
    public volatile int minIdle = default_min_idle;

    //
    public volatile long maxWaitTimeMillis = default_max_wait;
    public int notFullTimeoutRetryCount = 0;

    public volatile int validationQueryTimeout = -1;

    public volatile boolean inited = false;

    public volatile int queryTimeout;
    public volatile int transactionQueryTimeout;
    public AtomicLong createConntectionCount = new AtomicLong();

    public volatile int maxWaitNotEmptyThreadCount = -1;
    public volatile boolean accessToUnderlyingConnectionAllowed = true;
    public volatile long timeBetweenEvictionRunsMillis = default_time_millis_between_eviction_runs;

    public volatile long minEvictableIdleTimeMillis = default_min_evictable_idle_time_millis;
    public volatile long maxEvictableIdleTimeMillis = default_max_evictable_idle_time_millis;
    /**
     * 存活时间,当前时间减去创建时间
     */
    public volatile long maxAliveTimeOutTimeMillis = max_alive_time_millis;

    public volatile long removeAbandonedTimeoutMillis = 300 * 1000;

    public volatile long timeMillisBetweenConnectException = default_time_millis_between_connect_exception;

    public int connectionErrorRetryAttempts = 30;
    public boolean breakAfterAcquireFailure = false;

    public volatile Exception exceptionWhenCreateConntection;
    public volatile Exception lastError;
    public volatile long lastErrorTimeMillis;
    public volatile Exception lastCreateConntectionException;
    public volatile long lastCreateConntectionExceptionTimeMillis;
    //

    //
    public ReentrantLock activePooledConnectionToObjectMapLock = new ReentrantLock();
    public AtomicInteger creatingCount = new AtomicInteger();
    public AtomicLong createCount = new AtomicLong();
    public AtomicLong destroyConnectionCount = new AtomicLong();

    public int maxAllowCreateConnectionTaskCount = 3;
    public boolean failFast = false;
    /**
     * 不再失败
     */
    public AtomicBoolean failContinuous = new AtomicBoolean(false);

    public volatile boolean testOnBorrow = default_test_on_borrow;
    public volatile boolean testOnReturn = default_test_on_return;
    public volatile boolean testWhileIdle = default_while_idle;

    public Boolean useUnfairLock = null;

    public AbstractConnectionContexts(boolean fair) {
        lock = new ReentrantLock(fair);
        empty = lock.newCondition();
        notEmpty = lock.newCondition();
    }

    //
    public void setUseUnfairLock(boolean useUnfairLock) {
        boolean usefairLock = !useUnfairLock;
        if (lock.isFair() == usefairLock) {
            return;
        }
        if (!this.inited) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if (!this.inited) {
                    this.lock = new ReentrantLock(usefairLock);
                    this.useUnfairLock = useUnfairLock;
                    //
                    this.empty = this.lock.newCondition();
                    this.notEmpty = this.lock.newCondition();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public boolean isUseUnfairLock() {
        return lock.isFair();
    }

    //

    public void setTransactionQueryTimeout(int transactionQueryTimeout) {
        this.transactionQueryTimeout = transactionQueryTimeout;
    }

    public int getTransactionQueryTimeout() {
        if (transactionQueryTimeout <= 0) {
            return queryTimeout;
        }
        return transactionQueryTimeout;
    }

    public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
        this.removeAbandonedTimeoutMillis = (long) removeAbandonedTimeout * 1000;
    }

    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
        if (minEvictableIdleTimeMillis < 1000 * 30) {
            logger.error("minEvictableIdleTimeMillis should be greater than 30000");
        }

        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {
        if (maxEvictableIdleTimeMillis < 1000 * 30) {
            logger.error("maxEvictableIdleTimeMillis should be greater than 30000");
        }

        if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {
            throw new IllegalArgumentException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
        }

        this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
    }

    public void setMaxAliveTimeOutTimeMillis(long maxAliveTimeOutTimeMillis) {
        this.maxAliveTimeOutTimeMillis = maxAliveTimeOutTimeMillis;
    }

    public long getMaxAliveTimeOutTimeMillis() {
        return maxAliveTimeOutTimeMillis;
    }

    //

    //
    public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public long getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    //
    public void setMaxWaitNotEmptyThreadCount(int maxWaithThreadCount) {
        this.maxWaitNotEmptyThreadCount = maxWaithThreadCount;
    }

    public int getMaxWaitNotEmptyThreadCount() {
        return maxWaitNotEmptyThreadCount;
    }

    //
    public void setValidationQueryTimeout(int validationQueryTimeout) {
        this.validationQueryTimeout = validationQueryTimeout;
    }

    public int getValidationQueryTimeout() {
        return validationQueryTimeout;
    }

    //

    public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) {
        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
    }

    public boolean isAccessToUnderlyingConnectionAllowed() {
        return accessToUnderlyingConnectionAllowed;
    }

    //
    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }

    //

    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public boolean isTestOnReturn() {
        return testOnReturn;
    }

    //
    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    //

    //
    public void setDefaultCatalog(String defaultCatalog) {
        this.defaultCatalog = defaultCatalog;
    }

    public String getDefaultCatalog() {
        return this.defaultCatalog;
    }

    //
    public void setDefaultReadOnly(Boolean defaultReadOnly) {
        this.defaultReadOnly = defaultReadOnly;
    }

    public Boolean getDefaultReadOnly() {
        return defaultReadOnly;
    }

    //
    public void setDefaultAutoCommit(boolean defaultAutoCommit) {
        this.defaultAutoCommit = defaultAutoCommit;
    }

    public boolean isDefaultAutoCommit() {
        return defaultAutoCommit;
    }

    //
    public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
        this.defaultTransactionIsolationLevel = defaultTransactionIsolationLevel;
    }

    public Integer getDefaultTransactionIsolationLevel() {
        return defaultTransactionIsolationLevel;
    }

    //

    /**
     * @param queryTimeout seconds
     */
    public void setQueryTimeout(int queryTimeout) {
        this.queryTimeout = queryTimeout;
    }

    public int getQueryTimeout() {
        return queryTimeout;
    }

    //

    public void setMaxWaitTimeMillis(long maxWaitTimeMillis) {
        if (maxWaitTimeMillis == this.maxWaitTimeMillis) {
            return;
        }

        if (maxWaitTimeMillis > 0 && useUnfairLock == null && !this.inited) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if ((!this.inited) && (!lock.isFair())) {
                    this.lock = new ReentrantLock(true);
                    this.empty = this.lock.newCondition();
                    this.notEmpty = this.lock.newCondition();
                }
            } finally {
                lock.unlock();
            }
        }

        if (inited) {
            logger.error("maxWaitTimeMillis changed : " + this.maxWaitTimeMillis + " -> " + maxWaitTimeMillis);
        }

        this.maxWaitTimeMillis = maxWaitTimeMillis;
    }

    public long getMaxWaitTimeMillis() {
        return maxWaitTimeMillis;
    }

    //

    public void setNotFullTimeoutRetryCount(int notFullTimeoutRetryCount) {
        this.notFullTimeoutRetryCount = notFullTimeoutRetryCount;
    }

    public int getNotFullTimeoutRetryCount() {
        return notFullTimeoutRetryCount;
    }

    //
    //

    public void setInitialSize(int initialSize) {
        if (this.initialSize == initialSize) {
            return;
        }
        if (inited) {
            throw new ItHasInited();
        }
        this.initialSize = initialSize;
    }

    public int getInitialSize() {
        return initialSize;
    }

    //
    public void setMinIdle(int value) {
        if (value == this.minIdle) {
            return;
        }
        if (inited && value > this.maxActive) {
            throw new IllegalArgumentException("minIdle greater than maxActive, " + maxActive + " < " + this.minIdle);
        }
        if (minIdle < 0) {
            throw new IllegalArgumentException("minIdle must > 0");
        }
        this.minIdle = value;
    }

    public int getMinIdle() {
        return minIdle;
    }

    //

    public abstract void setMaxActive(int maxActive);

    public int getMaxActive() {
        return maxActive;
    }

    //
    public void setUsername(String username) {
        if (equals(this.username, username)) {
            return;
        }
        if (inited) {
            throw new ItHasInited();
        }
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    //

    public void setPassword(String password) {
        if (equals(this.password, password)) {
            return;
        }
        if (inited) {
            logger.info("password changed");
        }
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    //

    public abstract void setProperties(Properties properties);

    public Properties getProperties() {
        return properties;
    }

    //

    public void setConnectionProperties(String connectionPropertiesString) {
        if (connectionPropertiesString == null || connectionPropertiesString.trim().length() == 0) {
            setProperties(null);
            return;
        }

        String[] strings = connectionPropertiesString.split(";");
        Properties properties = new Properties();
        for (int i = 0; i < strings.length; i++) {
            String entry = strings[i];
            if (entry.length() > 0) {
                int index = entry.indexOf('=');
                String key = entry.substring(0, index);
                String value = entry.substring(index + 1);
                properties.setProperty(key, value);
            }
        }

        setProperties(properties);
    }

    //

    public void setUrl(String jdbcUrl) {
        if (equals(this.jdbcUrl, jdbcUrl)) {
            return;
        }
        if (inited) {
            throw new ItHasInited();
        }
        this.jdbcUrl = jdbcUrl;
    }

    public String getUrl() {
        return jdbcUrl;
    }

    //

    public boolean validateConnection(Connection connection) throws SQLException {
        if (connection == null) {
            throw new NullPointerException(Connection.class.getName());
        }
        if (connection.isClosed()) {
            throw new IllegalArgumentException("connection has closed");
        }
        String sql = "select 1";
        Statement statement = null;
        ResultSet resultSet = null;
        boolean ok = false;
        try {
            statement = connection.createStatement();
            if (validationQueryTimeout > 0) {
                statement.setQueryTimeout(validationQueryTimeout);
            }
            resultSet = statement.executeQuery(sql);
            ok = true;
        } finally {
            JdbcUtils.close(resultSet);
            JdbcUtils.close(statement);
        }
        return ok;
    }

    public Connection createConnection() throws SQLException {
        String url = this.getUrl();
        String username = getUsername();
        String password = getPassword();

        Properties properties = new Properties();
        if (getProperties() != null) {
            properties.putAll(getProperties());
        }

        properties.put("user", username.trim());
        properties.put("password", password.trim());

        Connection connection = null;
        creatingCount.incrementAndGet();
        try {
            // 创建项目
            connection = DriverManager.getConnection(url, properties);
            if (connection == null) {
                throw new NullPointerException("connect error, url " + url + ", driverClass ");
            }
            validateConnection(connection);
            initConnection(connection);
            setExceptionWhenCreateConntection(null);
        } catch (SQLException exception) {
            setExceptionWhenCreateConntection(exception);
            //
            if (connection != null) {
                JdbcUtils.close(connection);
            }

            throw new RuntimeException(exception);
        } catch (RuntimeException exception) {
            setExceptionWhenCreateConntection(exception);
            //
            if (connection != null) {
                JdbcUtils.close(connection);
            }

            throw exception;
        } catch (Exception exception) {
            createConntectionCount.incrementAndGet();
            setExceptionWhenCreateConntection(exception);
            //
            if (connection != null) {
                JdbcUtils.close(connection);
            }

            throw new RuntimeException(exception);
        } finally {
            creatingCount.decrementAndGet();
        }

        return connection;
    }

    public abstract void recycle(ConnectionContext connectionContext);

    protected void setExceptionWhenCreateConntection(Exception exception) {
        if (exception == null) {
            lock.lock();
            try {
                if (exceptionWhenCreateConntection != null) {
                    exceptionWhenCreateConntection = null;
                }
            } finally {
                lock.unlock();
            }
            return;
        }

        createConntectionCount.incrementAndGet();
        long now = System.currentTimeMillis();
        lock.lock();
        try {
            exceptionWhenCreateConntection = exception;
            lastCreateConntectionException = exception;
            lastCreateConntectionExceptionTimeMillis = now;
        } finally {
            lock.unlock();
        }
    }

    public void initConnection(Connection connection) throws SQLException {
        if (connection.getAutoCommit() != this.defaultAutoCommit) {
            connection.setAutoCommit(this.defaultAutoCommit);
        }
        if (connection.isReadOnly() != this.defaultReadOnly) {
            connection.setReadOnly(this.defaultReadOnly);
        }
        if (connection.getTransactionIsolation() != this.defaultTransactionIsolationLevel) {
            connection.setTransactionIsolation(this.defaultTransactionIsolationLevel);
        }
        // 不允许切换catalog

    }

    public abstract void discardConnection(Connection connection);

    //
    public void setMaxAllowCreateConnectionTaskCount(int maxAllowCreateConnectionTaskCount) {
        if (maxAllowCreateConnectionTaskCount < 1) {
            throw new IllegalArgumentException("maxAllowCreateConnectionTaskCount<1");
        }
        this.maxAllowCreateConnectionTaskCount = maxAllowCreateConnectionTaskCount;
    }

    public int getMaxAllowCreateConnectionTaskCount() {
        return maxAllowCreateConnectionTaskCount;
    }

    //
    public void setFailFast(boolean failFast) {
        this.failFast = failFast;
    }

    public boolean isFailFast() {
        return failFast;
    }
}
